﻿using System;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Common;
using System.Globalization;

using PublicDomain.Data.Csv.Metadata;

namespace PublicDomain.Data.Csv
{
    /// <summary>
    /// Defines interface for the error handler for errors which happen on reading a data row from the source CsvDataReader to the destination DataTable.
    /// </summary>
    /// <param name="exception">The exception which has been intercepted on reading/adding a data row.</param>
    /// <param name="dataRowIndex">The index of the CSV data row.</param>
    /// <returns>Returns true if handled exception should be re-thrown and false if it shouldn't.</returns>
    public delegate bool ReadDataRowErrorHandler(Exception exception, int csvDataRowIndex);

    /// <summary>
    /// Defines interface for the on read data row handler on reading a data row from the source CsvDataReader to the destination DataTable.
    /// </summary>
    /// <param name="dataRow">The data row.</param>
    /// <param name="csvDataRowIndex">Index of the CSV data row.</param>
    public delegate void ReadDataRowHandler(DataRow dataRow, int csvDataRowIndex);

    public static class CsvDataReaderAdapter
    {
        public static bool ReadToDataTable(this CsvDataReader dataReader, DataTable destinationDataTable, DataTableMapping dataTableMapping, int chunkSize, ReadDataRowHandler readDataRowHandler, ReadDataRowErrorHandler readDataRowErrorHandler)
        {
            if (dataReader == null)
            {
                throw new ArgumentNullException("dataReader");
            }

            if (destinationDataTable == null)
            {
                throw new ArgumentNullException("destinationDataTable");
            }

            if (dataTableMapping == null)
            {
                throw new ArgumentNullException("dataTableMapping");
            }

            var columnOrdinalMappings = new int[dataTableMapping.ColumnMappings.Count, 2];

            for (int i = 0; i < dataTableMapping.ColumnMappings.Count; i++)
            {
                columnOrdinalMappings[i, 0] = dataReader.GetOrdinal(dataTableMapping.ColumnMappings[i].SourceColumn);
                columnOrdinalMappings[i, 1] = destinationDataTable.Columns[dataTableMapping.ColumnMappings[i].DataSetColumn].Ordinal;
            }

            destinationDataTable.Clear();

            int rowIndex = 0;
            var result = false;
            var dataReaderReadStatus = true;
            while (rowIndex < chunkSize && dataReaderReadStatus)
            {
                try
                {
                    dataReaderReadStatus = dataReader.Read();  // The call to the CsvDataReader.Read has been moved inside the try..catch block in order to handle errors per data row.
                    if (!dataReaderReadStatus)
                    {
                        break;
                    }

                    var dataRow = destinationDataTable.NewRow();

                    for (int i = 0; i < columnOrdinalMappings.GetLength(0); i++)
                    {
                        var sourceColumnIndex = columnOrdinalMappings[i, 0];
                        var destinationColumnIndex = columnOrdinalMappings[i, 1];

                        if (dataReader.IsDBNull(sourceColumnIndex))
                        {
                            dataRow[destinationColumnIndex] = DBNull.Value;
                            continue;
                        }

                        dataRow[destinationColumnIndex] = dataReader[sourceColumnIndex];
                    }

                    destinationDataTable.Rows.Add(dataRow);

                    if (readDataRowHandler != null)
                    {
                        readDataRowHandler(dataRow, dataReader.CurrentDataRowIndex);
                    }
                }
                catch (Exception exception)
                {
                    if (readDataRowErrorHandler == null)
                    {
                        throw;
                    }

                    if (readDataRowErrorHandler(exception, dataReader.CurrentDataRowIndex))
                    {
                        throw;
                    }
                }

                rowIndex++;
                result = true;
            }

            return result;
        }

        public static DataTable CreateDataTable(string tableName, Collection<CsvColumnDefinition> csvColumnsDefinitions, out DataTableMapping defaultDataTableMapping)
        {
            if (csvColumnsDefinitions == null)
            {
                throw new ArgumentNullException("csvColumnsDefinitions");
            }

            var result = String.IsNullOrWhiteSpace(tableName) ? new DataTable() : new DataTable(tableName);
            result.Locale = CultureInfo.InvariantCulture;

            var defaultDataColumnMappings = new DataColumnMapping[csvColumnsDefinitions.Count];

            for (int i = 0; i < csvColumnsDefinitions.Count; i++)
            {
                var csvColumnsDefinition = csvColumnsDefinitions[i];

                var column = new DataColumn(csvColumnsDefinition.ColumnName, csvColumnsDefinition.FieldType);
                column.AllowDBNull = true;

                result.Columns.Add(column);

                defaultDataColumnMappings[i] = new DataColumnMapping(csvColumnsDefinition.ColumnName, column.ColumnName);
            }

            defaultDataTableMapping = new DataTableMapping(String.Empty, tableName, defaultDataColumnMappings);

            return result;
        }
    }
}
